using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Gko.Utils;
using Gko.Wpf.Mvvm.Interfaces;

namespace Gko.Wpf.Mvvm
{
	public abstract class MediatorBase<TMessageId> where TMessageId : IEquatable<TMessageId>
	{
		private readonly MessageToActionsMap<TMessageId> map;
		private readonly MediatorOptions options;
		private readonly Dictionary<TMessageId, object> lastNotifiedValues = new Dictionary<TMessageId, object>();

		protected MediatorBase()
			: this(MediatorOptions.None)
		{
		}

		public MediatorOptions Options
		{
			get
			{
				return this.options;
			}
		}

		protected MediatorBase(MediatorOptions options)
		{
			this.options = options;
			this.map = new MessageToActionsMap<TMessageId>();
		}

		protected TValue GetLastNotifiedValue<TValue>(TMessageId messageId, TValue defaultValue)
		{
			return (TValue)this.lastNotifiedValues.GetValueOrDefault(messageId, defaultValue);
		}

		protected void SetValue<TValue>(TMessageId messageId, TValue value)
		{
			this.lastNotifiedValues[messageId] = value;
		}

		protected void Add<TValue>(TMessageId messageId, Action<TValue> action)
		{
			this.map.Add(messageId, action);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		protected IEnumerable<Action<TValue>> GetActions<TValue>(TMessageId messageId)
		{
			return this.map.GetActions<TValue>(messageId);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		protected void CallActions<TValue>(IEnumerable<Action<TValue>> actions, TValue value, MediatorOptions overridenOptions)
		{
			if (overridenOptions.IsBackground()) {
				this.DoCallActionsInBackground(actions, value, overridenOptions);
			} else {
				this.DoCallActions(actions, value, overridenOptions);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		protected void CallActions<TValue>(IEnumerable<Action<TValue>> actions, TValue value, SynchronizationContext context, MediatorOptions overridenOptions)
		{
			if (overridenOptions.IsBackground()) {
				this.DoCallActionsInBackground(actions, value, context, overridenOptions);
			} else {
				this.DoCallActions(actions, value, context, overridenOptions);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		protected void DoCallActionsInBackground<TValue>(IEnumerable<Action<TValue>> actions, TValue value, MediatorOptions overridenOptions)
		{
			Background.Invoke(this.DoCallActions, actions, value, overridenOptions);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		protected void DoCallActions<TValue>(IEnumerable<Action<TValue>> actions, TValue value, MediatorOptions overridenOptions)
		{
			if (overridenOptions.IsParallel()) {
				actions = actions.Select(a => a.Convert(act => v => act.InvokeAsync(v)));
			}
			DoCall(actions, value);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		protected void DoCallActionsInBackground<TValue>(IEnumerable<Action<TValue>> actions, TValue value, SynchronizationContext context, MediatorOptions overridenOptions)
		{
			context.BeginInvoke(this.DoCallActions, actions, value, context, overridenOptions);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		protected void DoCallActions<TValue>(IEnumerable<Action<TValue>> actions, TValue value, SynchronizationContext context, MediatorOptions overridenOptions)
		{
			Action<Action<TValue>, TValue> invoker;
			if (overridenOptions.IsParallel()) {
				invoker = (act, v) => context.BeginInvoke(act, v);
			}else {
				invoker = (act, v) => context.Invoke(act, v);
			}
			
			DoCall(actions.Select(a => a.Convert(act => v => invoker(act, v))), value);
		}

		private static void DoCall<TValue>(IEnumerable<Action<TValue>> actions, TValue value)
		{
			foreach (var action in actions) {
				action(value);
			}
		}
	}
}